package cn.edu.neu.lab603.cloudeval.entity

import java.util.concurrent.atomic.{AtomicInteger, AtomicLong}

import cn.edu.neu.lab603.cloudeval.CloudEval
import cn.edu.neu.lab603.cloudeval.entity.DataCenter.UpdateEvent
import cn.edu.neu.lab603.cloudeval.entity.PhysicalMachine.Status
import cn.edu.neu.lab603.des.{SimEntity, SimEvent}
import com.typesafe.scalalogging.LazyLogging

import scala.collection.{mutable, Map => AbsMap}

/** 实体机类型。
  *
  * Created by yfwz100 on 16/9/12.
  */
class PhysicalMachine(private[entity] var dataCenter: DataCenter,
                      val capacity: Map[String, Int]) extends SimEntity with LazyLogging with Cloneable {

  private implicit val self = this
  /** 获取物理主机的唯一标识。 */
  val hostNo: Long = PhysicalMachine.idGen
  private val vmList = new mutable.Queue[VirtualMachine]
  private val remainResMap = new mutable.HashMap[String, Int]
  private var status: PhysicalMachine.Status = PhysicalMachine.Status.OFF

  // 初始化剩余资源量
  remainResMap ++= capacity

  def this(capacity: Map[String, Int]) = this(null, capacity)

  /** 根据资源需求分配虚拟机。
    *
    * @param resMap 资源需求
    * @return 虚拟机
    */
  def allocateVirtualMachine(resMap: Map[String, Int]): VirtualMachine = {
    if (vmList.nonEmpty) {
      vmList.filter(_.getStatus == VirtualMachine.Status.ON).filter(_.getCloudlet == null).foreach { vm =>
        if (vm.fit(resMap)) {
          return vm
        }
      }
      createVirtualMachineIfSufficient(resMap)
    } else {
      createVirtualMachineIfSufficient(resMap)
    }
  }

  /** 在虚拟机资源充足的情况下，根据资源需求创建虚拟机。
    *
    * @param resMap 资源需求
    * @return 创建的虚拟机
    */
  def createVirtualMachineIfSufficient(resMap: Map[String, Int]): VirtualMachine = {
    if (resMap.forall(v => v._2 <= remainResMap.getOrElse(v._1, 0))) {
      createVirtualMachine(resMap)
    } else throw new NoEnoughResForVM
  }

  /** 根据资源需求直接创建虚拟机。
    *
    * @param resMap 资源需求
    * @return 创建的虚拟机
    */
  def createVirtualMachine(resMap: Map[String, Int]): VirtualMachine = {
    val vm = new VirtualMachine(this, resMap)
    vm.boot()

    vmList += vm

    vm
  }

  /** 复制虚拟机到当前物理机中。
    *
    * 待复制虚拟机需要满足以下条件：
    * 1. 虚拟机不应该被自身所拥有；
    * 2. 虚拟机应该承担一个计算任务；
    * 3. 虚拟机应该是关闭的。
    *
    * @param vm 待复制虚拟机
    */
  def copyVirtualMachine(vm: VirtualMachine): Unit = {
    assert(vm.host != this, "Virtual machine should not be owned by self.")
    assert(vm.getCloudlet != null, "Virtual machine should undertake a task.")

    if (vm.getSpecResMap.forall(p => p._2 <= remainResMap.getOrElse(p._1, 0))) {
      vmList enqueue vm

      // 复制虚拟机到当前主机
      val spec = vm.getSpecResMap
      context.submitTask(
        (spec("ram") / (spec("bw") * 0.1)).toInt,
        new PhysicalMachine.VMMigrateInEvent(vm)
      )
    }
  }

  /** 取消虚拟机的分配。
    *
    * @param cloudlet 云计算任务
    */
  def deallocateVirtualMachine(cloudlet: CloudTask): Unit = {
    deallocateVirtualMachine(cloudlet.getExecutionContext)
  }

  /** 取消虚拟机的分配。
    *
    * @param vm 虚拟机
    */
  def deallocateVirtualMachine(vm: VirtualMachine): Unit = {
    vmList.dequeueAll(_ == vm).foreach(_.destroy())
  }

  /** 获取当前剩余的资源映射（百分比）。
    *
    * @return 资源映射
    */
  def getRemainResPct: scala.collection.Map[String, Double] = remainResMap.map(p => (p._1, p._2.toDouble / capacity(p._1)))

  def getRemainResMap: scala.collection.Map[String, Int] = remainResMap

  /** 获取当前已经占用的资源（百分比）。
    *
    * @return 资源映射
    */
  def getUsedResPct: scala.collection.Map[String, Double] =
  remainResMap.map(p => (p._1, (capacity(p._1) - p._2).toDouble / capacity(p._1)))

  def getUsedResMap: scala.collection.Map[String, Int] = remainResMap.map(p => (p._1, capacity(p._1) - p._2))

  /** 获得虚拟机列表。 */
  def getVMList: Seq[VirtualMachine] = vmList

  /** 启动机器。 */
  def boot(): Unit = {
    if (getStatus == PhysicalMachine.Status.OFF) {
      setStatus(PhysicalMachine.Status.BOOT)

      context.submitTask(PhysicalMachine.BOOT_DELAY, new PhysicalMachine.BootEvent)
    } else if (getStatus == PhysicalMachine.Status.SHUTDOWN) {
      setStatus(PhysicalMachine.Status.ON)
    } else {
      logger.trace(s"Host#$hostNo received 'boot' when it's not off.")
    }
  }

  /** 设置实体机的状态。
    *
    * @param status 状态值
    */
  private def setStatus(status: PhysicalMachine.Status) = this.status = status

  /** 获得实体机的状态。
    *
    * @return 状态值
    */
  def getStatus: Status = this.status

  /** 更新资源分配 */
  def update(clock: Int): Unit = {
    // 首先实现一个抢占式优先调度器，保证至少能够执行部分虚拟机。
    remainResMap.clear()
    remainResMap ++= capacity

    val remain = new AtomicInteger(vmList.size)

    // 更新所有虚拟机
    while (remain.getAndDecrement() > 0) {
      val vm = vmList dequeue()

      if (vm.isRunning) {
        val usedReqRes = vm.update(clock, remainResMap)

        for ((k, v) <- usedReqRes) {
          remainResMap(k) -= v
        }
      }

      // 发送网络数据。每次只发送一个数据包。
      if (vm.net.packetQueue.nonEmpty) {
        if (remainResMap.getOrElse("bw", 0) >= vm.net.packetQueue.front.length) {
          val pkt = vm.net.packetQueue.dequeue()

          // TODO 网络传输延迟
          context.submitTask(1, new DataCenter.NetworkTransferEvent(pkt))

          remainResMap("bw") -= pkt.length
        }
      }

      vmList enqueue vm
    }

    // 此处不应该实现公平调度器。在目前的调度环境限制下，可能导致所有虚拟机都不能正常运作。
  }

  def context: CloudEval = dataCenter.context

  /** 关闭机器。 */
  def shutdown(): Unit = {
    if (getStatus == PhysicalMachine.Status.ON) {
      setStatus(PhysicalMachine.Status.SHUTDOWN)

      logger.debug(s"[${context.getCurrentClock}] $this is going to shut down.")

      context.submitTask(PhysicalMachine.SHUTDOWN_DELAY, new PhysicalMachine.ShutdownEvent)
    } else if (getStatus == PhysicalMachine.Status.BOOT) {
      setStatus(PhysicalMachine.Status.OFF)
    } else {
      logger.trace(s"Host#$hostNo received 'shutdown' when it's not on.")
    }
  }

  override def toString = s"Host#$hostNo(core=${capacity("core")}, vms=${vmList.size})"

  /** 预复制该虚拟机。
    *
    * @param vm 该虚拟机
    */
  private[entity] def preCopyVirtualMachine(vm: VirtualMachine): VirtualMachine = {
    assert(vm.host != this, "Virtual machine should not be owned by self.")
    assert(vm.getCloudlet != null, "Virtual machine should undertake a task.")

    // 启动影子虚拟机
    val newVM = new VMHolder(vm)
    vmList enqueue newVM

    newVM
  }

  /** 接收该预复制虚拟机。
    * 如果该虚拟机不是预复制虚拟机，那么将发生错误。
    *
    * @param vm 预复制虚拟机
    */
  private[entity] def postCopyVirtualMachine(vm: VirtualMachine): Unit = {
    val holder = vm.asInstanceOf[VMHolder]
    context.submitTask(
      (Math.abs(holder.getUsedResMap("ram") - holder.vm.getUsedResMap("ram")) / (capacity("bw") * 0.1) + 1).toInt,
      new PhysicalMachine.VMMigrateInEvent(vm)
    )
  }

  private class VMHolder(val vm: VirtualMachine) extends VirtualMachine(this, vm.getSpecResMap) {

    setStatus(VirtualMachine.Status.MIGRATING)

    private val curResMap: mutable.HashMap[String, Int] = mutable.HashMap(
      "ram" -> vm.getUsedResMap("ram"),
      "mips" -> 200
    )
    private var prevResMap: scala.collection.Map[String, Int] = _

    override def host_=(target: PhysicalMachine): Unit = vm.host = target

    override def update(clock: Int, remainResMap: AbsMap[String, Int]): AbsMap[String, Int] = {
      if (prevResMap == null) {
        prevResMap = vm.getUsedResMap
      } else if (vm.getUsedResMap("ram") <= prevResMap("ram")) {
        curResMap("mips") = 200 // 用于复制
      } else {
        curResMap("mips") = 50 // 空闲占用
      }
      // 如果已经运行结束，则停止该虚拟机的复制
      if (vm.getCloudlet.getCursor >= vm.getCloudlet.length) {
        vmList dequeueAll (_ == this)
      }
      curResMap
    }

    override def boot(): Unit = {
      vmList dequeueAll (_ == this)
      vmList enqueue vm
      vm.setStatus(VirtualMachine.Status.ON)
    }

    override def shutdown(): Unit = {
      throw new UnsupportedOperationException
    }

    override def destroy(): Unit = {
      vm.host.deallocateVirtualMachine(vm)
    }

    override def getUsedResMap: scala.collection.Map[String, Int] = curResMap

    override def getReqResMap: scala.collection.Map[String, Int] = curResMap

    override def getCloudlet: CloudTask = vm.getCloudlet

    override def toString = s"VM#$vmNo(${vm.vmNo})"
  }
}

object PhysicalMachine {
  type Status = Status.Value
  val BOOT_DELAY = 10
  val SHUTDOWN_DELAY = 10
  val TRANSFER_DELAY = 10
  private val idCnt = new AtomicLong()
  private val cache = new mutable.HashMap[Long, PhysicalMachine]()

  private def idGen(implicit host: PhysicalMachine): Long = {
    val _id = idCnt.getAndIncrement()
    cache(_id) = host
    _id
  }

  /** 主机已关机事件。
    *
    * @param host 主机
    */
  class ShutdownEvent(implicit val host: PhysicalMachine) extends SimEvent[CloudEval] with LazyLogging {
    override def execute(context: CloudEval): Unit = {
      if (host.getStatus == Status.SHUTDOWN) {
        host.setStatus(Status.OFF)

        logger.debug(s"[${context.getCurrentClock}] Host#${host.hostNo} is turned off.")
      }
    }

    override def effective: Boolean = host.getStatus == Status.SHUTDOWN || host.getStatus == Status.OFF
  }

  /** 主机已启动事件。
    *
    * @param host 主机
    */
  class BootEvent(implicit val host: PhysicalMachine) extends SimEvent[CloudEval] with LazyLogging {
    private implicit val dc = host.dataCenter

    override def execute(context: CloudEval): Unit = {
      if (host.getStatus == Status.BOOT) {
        host.setStatus(Status.ON)

        logger.debug(s"[${context.getCurrentClock}] Host#${host.hostNo} is turned on.")

        context.submitTaskOnNextTick(new UpdateEvent)
      }
    }

    override def effective: Boolean = host.getStatus == Status.BOOT || host.getStatus == Status.ON
  }

  /** 虚拟机迁移事件。
    *
    * @param vm 待迁移虚拟机
    * @param pm 目的主机
    */
  class VMMigrateInEvent(val vm: VirtualMachine)(implicit val pm: PhysicalMachine) extends SimEvent[CloudEval] with LazyLogging {

    override def execute(context: CloudEval): Unit = {
      // 恢复执行状态
      vm.host = pm

      logger.debug(s"[${context.getCurrentClock}] $vm has been migrated to Host#${pm.hostNo}")

      vm.boot()
    }

  }

  /** 状态 */
  object Status extends Enumeration {
    val ON, BOOT, SHUTDOWN, OFF = Value
  }

}

class NoEnoughResForVM(val msg: String = null) extends Exception(msg)
